package simpleftp.server;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Properties;
import java.util.concurrent.Callable;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;


import simpleftp.client.control.Command;

/**
 * ServeurFTP is a simple package that implements a Java FTP server. With
 * ServeurFTP, you can connect to an FTP client and download multiple files.
 */
public class FTPServer implements Callable<Void> {
	private static boolean DEBUG = false;
	private String response, user;
	private int validations;
	private File f;
	private TreeModel t;
	private StringBuilder sb;
	private int portnum1, portnum2;
	private Properties p;
	private ServerSocket dl;
	private Socket dl2;
	private FileInputStream fis;
	private Socket socket = null;
	private BufferedReader reader = null;
	private BufferedWriter writer = null;
	private ObjectOutputStream objectWriter = null;

	public FTPServer(Socket s) {
		socket = s;
		validations = 0;
		try {
			p = new Properties();
			fis = new FileInputStream("users.properties");
			p.load(fis);
			try {
				reader = new BufferedReader(new InputStreamReader(
						socket.getInputStream()));
				writer = new BufferedWriter(new OutputStreamWriter(
						socket.getOutputStream()));
				objectWriter = new ObjectOutputStream(socket.getOutputStream());

				sendLine("220 ");
				validations++;
				f = new File(System.getProperty("user.dir"));
				System.out.println("FTP Server : new client from "
						+ socket.getInetAddress().getHostAddress());
			} catch (IOException e) {
				try {
					sendLine("error");
				} catch (IOException e1) {
					e1.printStackTrace();
				}
				e.printStackTrace();
			}
		} catch (IOException ioex) {
			System.out.println("Error: Properties File didn't load");
			try {
				sendLine("error ");
			} catch (IOException e) {
				e.printStackTrace();
			}
			ioex.printStackTrace();
		}
	}

	public Void call() {
		try {
			response = readLine();
			if (response.startsWith("USER")) {
				if (p.containsKey(response.substring(5))) {
					user = response.substring(5);
					System.out.println("Server knows user " + response);
					sendLine("331 ");
					validations++;
				} else {
					System.out.println("Server does no know user " + response);
					sendLine("error ");
				}
			}
			response = readLine();
			if (response.startsWith("PASS")) {
				if (p.get((String) user).equals(response.substring(5))) {
					System.out.println("Server validates pass " + response);
					sendLine("230 ");
					validations++;
				} else {
					sendLine("error ");
				}
			}

		} catch (IOException e) {
			// TODO Auto-generated catch block
			try {
				sendLine("error ");
			} catch (IOException e1) {
				// TODO Auto-generated catch block
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

		while (validations == 3) {
			try {
				response = readLine();

				switch (Command.valueOf(response.split(" ")[0])) {

				case PWD:
					sendLine("257 " + System.getProperty("user.dir"));
					break;

				case LIST:
					sb = new StringBuilder("200 ");
					String[] fileNames = f.list();
					for (String fileName : fileNames) {
						sb = sb.append(fileName + "|");
					}
					sendLine(sb.toString());
					if (f == null)
						f = new File(System.getProperty("user.dir"));
					//uncomment these 4 lines to pass in object mode
//					DefaultMutableTreeNode root = new DefaultMutableTreeNode(f.getName());
//					populateTree(root,f);
//					TreeModel t = new DefaultTreeModel(root,true);
//					sendObject(t);
					//comment these two lines to pass in object mode
					JSONObject obj = populateTree(f);					
					sendLine(obj.toString());
					break;

				case CWD:
					System.out.println("CWD");
					System.setProperty("user.dir",
							System.getProperty("user.dir").toString()
									+ response.substring(4));
					f = new File(System.getProperty("user.dir"));
					sendLine("250 ");
					break;

				case PASV:
					pasv();
					break;

				case STOR:
					stor();
					break;
				case RETR:
					retr();
					break;

				case QUIT:
					System.out.println("QUIT :Goodbye");
					validations = 0;
					socket.close();
					break;

				default:
					System.out.println("Unknown Command.");
					sendLine("error");
					break;

				}
			} catch (IOException e) {
				try {
					sendLine("error");

				} catch (IOException e1) {

					e1.printStackTrace();
					validations = 0;
				}
				e.printStackTrace();
				validations = 0;
			}
				catch (JSONException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		}

		Void Void = null;
		return Void;
	}



	private void sendObject(Object e) throws IOException {
		if (socket == null || objectWriter == null) {
			throw new IOException("FTPServer is not connected.");
		}
		try {
			objectWriter.writeObject(e);
			objectWriter.flush();
			if (DEBUG) {
				System.out.println("> ObjectSent : " + e.toString());
			}
		} catch (IOException ex) {
			socket = null;
			throw ex;
		}
	}

	/**
	 * Sends a raw command to the FTP server.
	 */

	private void sendLine(String line) throws IOException {
		if (socket == null) {
			throw new IOException("FTPServer is not connected.");
		}
		try {
			writer.write(line + "\r\n");
			writer.flush();
			if (DEBUG) {
				System.out.println("> " + line);
			}
		} catch (IOException e) {
			socket = null;
			throw e;
		}
	}

	private String readLine() throws IOException {
		String line = reader.readLine();
		if (DEBUG) {
			System.out.println("< " + line);
		}
		return line;
	}

	private void retr() {
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			FileInputStream filestream = new FileInputStream(new File(
					System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			BufferedInputStream input = new BufferedInputStream(filestream);

			BufferedOutputStream output = new BufferedOutputStream(
					dl2.getOutputStream());
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = input.read(buffer)) != -1) {
				output.write(buffer, 0, bytesRead);
			}
			output.flush();
			output.close();
			input.close();
			sendLine("226 ");
			dl2.close();
			System.out.println("RETR " + response.split(" ")[1]
					+ " : File sended");
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}

	}

	private void stor() {
		try {
			dl2 = dl.accept();
			sendLine("150 ");
			OutputStream os = new FileOutputStream(new File(
					System.getProperty("user.dir")
							+ System.getProperty("file.separator")
							+ response.substring(5)));
			System.out.println("write file to: "
					+ System.getProperty("user.dir")
					+ System.getProperty("file.separator")
					+ response.substring(5));
			BufferedInputStream is = new BufferedInputStream(
					dl2.getInputStream());
			byte[] buffer = new byte[4096];
			int bytesRead = 0;
			while ((bytesRead = is.read(buffer)) != -1) {
				os.write(buffer, 0, bytesRead);
			}
			os.flush();
			os.close();
			is.close();
			sendLine("226 ");
			dl2.close();
			System.out.println("STOR " + response.split(" ")[1]
					+ " : File received");
		} catch (IOException e) {
			try {
				sendLine("error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}

	private void pasv() {
		try {
			dl = new ServerSocket(0);
			portnum1 = dl.getLocalPort() / 256;
			portnum2 = dl.getLocalPort() % 256;
			sendLine("227 ("
					+ socket.getLocalAddress().toString().substring(1)
							.replace('.', ',') + "," + portnum1 + ","
					+ portnum2 + ")");
			System.out.println("PASV success");
		} catch (IOException e) {
			try {
				sendLine("error ");
				System.out.println("pasv error");
			} catch (IOException e1) {
				e1.printStackTrace();
			}
			e.printStackTrace();
		}
	}

	
	private void populateTree(DefaultMutableTreeNode userRoot, File root) {
		for (File i : root.listFiles()) {
			DefaultMutableTreeNode t = new DefaultMutableTreeNode(i.getName());
			userRoot.add(t);
			if (i.isDirectory())
				populateTree(t, i);
			else
				t.setAllowsChildren(false);
		}
	}
	
	private JSONObject populateTree( File feed) throws JSONException {
		
		JSONArray list = new JSONArray();
		JSONObject obj =new JSONObject();
		for (File i : feed.listFiles()) {			
			if (i.isDirectory())
				list.put (populateTree( i));
			else list.put(i.getName());				
		}		
		obj.put(feed.getName(),list);
		return obj;
	}

	
	public static void setDebug(boolean b) {
		DEBUG = b;
	}
}
